﻿#region Imports
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using CommonUtilities.Enums;
using CommonUtilities.Exceptions;
using CommonUtilities.Messages;
using CommonUtilities.Properties;
using CommonUtilities.Util;
#endregion

namespace CommonUtilities.Extensions
{
    /// <summary>
    /// Provides extension methods defined on classes inheriting from <code>System.Attribute</code>
    /// </summary>
    public static class AttributeExtensions
    {
        #region Extension methods
        #region Type attribute extensions
        /// <summary>
        /// Gets the attributes of the specified type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <returns></returns>
        public static AttributeCollection GetAttributes<TClass>(this TClass value)
          where TClass : class, new()
        {
            if (value.IsNull()) value = new TClass { };
            return TypeDescriptor.GetAttributes(typeof(TClass));
        }

        /// <summary>
        /// Gets the specified attribute of the specified type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <typeparam name="TAttrib">Attribute to be searched</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <returns></returns>
        public static TAttrib GetAttribute<TClass, TAttrib>(this TClass value)
            where TClass : class, new()
            where TAttrib : Attribute
        {
            if (value.IsNull()) value = new TClass { };
            return (TAttrib)value.GetAttributes()[typeof(TAttrib)];
        }

        /// <summary>
        /// Gets the description of the class
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <returns></returns>
        public static string GetClassDescription<TClass>(this TClass value, params object[] param)
           where TClass : class, new()
        {
            if (value.IsNull()) value = new TClass { };
            string desc = Convert.ToString(value.GetAttributePropertyValue<TClass, DescriptionAttribute>("Description"));

            return !desc.IsNullOrEmpty() ? desc.FormatTextFromParams(param) : null;
        }

        /// <summary>
        /// Gets the properties of the specified attribute of the specified type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <typeparam name="TAttrib">Attribute to be searched for its properties</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <returns></returns>
        public static IEnumerable<PropertyInfo> GetAttributeProperties<TClass, TAttrib>(this TClass value)
            where TClass : class, new()
            where TAttrib : Attribute
        {
            if (value.IsNull()) value = new TClass { };
            TAttrib attr = value.GetAttribute<TClass, TAttrib>();

            return attr.GetType().GetProperties();
        }

        /// <summary>
        /// Gets the specified property of the specified attribute of the specified type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <typeparam name="TAttrib">Attribute to be searched for its property</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <param name="propertyName">Name of the property of the specified attribute to be searched</param>
        /// <returns></returns>
        public static PropertyInfo GetAttributeProperty<TClass, TAttrib>(this TClass value, string propertyName)
            where TClass : class, new()
            where TAttrib : Attribute
        {
            return value.GetAttributeProperties<TClass, TAttrib>().Where(prop => prop.Name == propertyName).FirstOrDefault();  //.GetType().GetProperty(propertyName);
        }

        /// <summary>
        /// Gets the value of the specified property of the specified attribute of the specified type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <typeparam name="TAttrib">Attribute to be searched for value of its property</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <param name="propertyName">Name of the property of the specified attribute to be searched</param>
        /// <returns></returns>
        public static object GetAttributePropertyValue<TClass, TAttrib>(this TClass value, string propertyName)
            where TClass : class, new()
            where TAttrib : Attribute
        {
            if (value.IsNull()) value = new TClass { };
            PropertyInfo propInfo = value.GetAttributeProperty<TClass, DescriptionAttribute>(propertyName);
            Attribute attr = value.GetAttribute<TClass, TAttrib>();

            return propInfo.GetValue(attr);
        }
        #endregion Type attribute extensions

        #region Property attribute extensions
        /// <summary>
        /// Gets the attributes of the specified property of a type
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <param name="propertyName">Name of the property to be analyzed</param>
        /// <returns></returns>
        public static IEnumerable<Attribute> GetPropertyAttributes<TClass>(this TClass value, string propertyName)
            where TClass : class, new()
        {
            if (value.IsNull()) value = new TClass { };
            return value.GetType().GetProperty(propertyName).GetCustomAttributes(true)._Cast<Attribute>();
        }

        /// <summary>
        /// Gets the specified attribute of the specified property of a non-abstract type
        /// <para>where public parameter-less constructor is NOT available</para>
        /// <para>TIP: Use as generic method</para>
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <param name="propertyName">Name of the property to be analyzed</param>
        /// <param name="attribName">Name of the attribute to be searched</param>
        /// <returns>Attribute specified in <paramref name="attribName"/></returns>
        public static Attribute GetPropertyAttribute<TClass>(this TClass value, string propertyName, string attribName)
            where TClass : class, new()
        {
            try
            {
                IEnumerable<Attribute> attributes = value.GetPropertyAttributes(propertyName);

                // for non-abstract types where public parameter-less constructor is not available; also generic method
                if (attributes._Select(attr => attr.GetAttribName())._Contains(ExtractAttribName(attribName)))
                    return attributes._Where(attr => attr.GetAttribName() == ExtractAttribName(attribName)).FirstOrDefault();
                else return null;
            }
            catch (Exception ex)
            {
                MessageBoxUtil.Show(Debugger.IsAttached, ex.StackTrace, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets the specified attribute of the specified property of a non-abstract type ONLY
        /// <para>where public parameter-less constructor is AVAILABLE</para>
        /// </summary>
        /// <typeparam name="TClass">Type to which the property belongs</typeparam>
        /// <typeparam name="TAttrib">Attribute to be searched</typeparam>
        /// <param name="value">Object of the type to be looked into</param>
        /// <param name="propertyName">Name of the property to be analyzed</param>
        /// <returns>Attribute specified by <paramref name="TAttrib"/></returns>
        public static TAttrib GetPropertyAttribute<TClass, TAttrib>(this TClass value, string propertyName)
            where TClass : class, new()
            where TAttrib : Attribute
        {
            return (TAttrib)value.GetPropertyAttribute(propertyName, typeof(TAttrib).Name);
        }


        /// <summary>
        /// RKD - NOT TESTED
        /// <para>TIP: Use as generic method</para>
        /// </summary>
        /// <typeparam name="TClass"></typeparam>
        /// <typeparam name="TOut"></typeparam>
        /// <param name="value"></param>
        /// <param name="propertyName"></param>
        /// <param name="attribName"></param>
        /// <param name="attribPropName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static TOut GetPropertyAttributeProperty<TClass, TOut>(this TClass value, string propertyName, string attribName, string attribPropName, TOut defaultValue = default(TOut))
             where TClass : class, new()
        {
            return value.GetPropertyAttribute<TClass>(propertyName, attribName).Coalesce(attribPropName, defaultValue);
        }

        /// <summary>
        /// 
        /// RKD - NOT TESTED
        /// </summary>
        /// <typeparam name="TClass"></typeparam>
        /// <typeparam name="TAttrib"></typeparam>
        /// <typeparam name="TOut"></typeparam>
        /// <param name="value"></param>
        /// <param name="propertyName"></param>
        /// <param name="attribPropName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static TOut GetPropertyAttributeProperty<TClass, TAttrib, TOut>(this TClass value, string propertyName, string attribPropName, TOut defaultValue = default(TOut))
            where TClass : class, new()
            where TAttrib : Attribute
        {
            return value.GetPropertyAttribute<TClass>(propertyName, typeof(TAttrib).Name).Coalesce(attribPropName, defaultValue);
        }
        #endregion Property attribute extensions

        #region Enum attribute extensions
        public static IEnumerable<Attribute> GetEnumAttributes<TEnum>(this TEnum value)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            Type type = value.GetType();
            string name = Enum.GetName(type, value);

            List<Attribute> lstAttribute = new List<Attribute>();
            if (!name.IsNull()) type.GetField(name).GetCustomAttributes(true)._ForEach(attr => lstAttribute.Add((Attribute)attr));

            return lstAttribute;
        }

        public static TAttrib GetEnumAttribute<TEnum, TAttrib>(this TEnum value)
            where TEnum : struct, IComparable, IConvertible, IFormattable
            where TAttrib : Attribute, new()
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            if (!Utilities.HasDefaultCtor<TAttrib>())
                throw new DefaultCtorNotAvailableException<TAttrib>(null);

            Attribute attribute = new TAttrib();
            return (TAttrib)GetEnumAttributes(value)
                .Where(attr => attr.TypeId == attribute.TypeId).FirstOrDefault();
        }

        public static Attribute GetEnumAttribute<TEnum>(this TEnum value, string attribName)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            IEnumerable<Attribute> attributes = value.GetEnumAttributes();

            // for non-abstract types where public parameter-less constructor is not available
            if (attributes._Select(attr => attr.GetAttribName())._Contains(ExtractAttribName(attribName)))
                return attributes._Where(attr => attr.GetAttribName() == ExtractAttribName(attribName)).FirstOrDefault();
            else return null;
        }

        public static TOut GetEnumAttributeProperty<TEnum, TAttrib, TOut>(this TEnum value, string attribPropName, TOut defaultValue = default(TOut))
            where TEnum : struct, IComparable, IConvertible, IFormattable
            where TAttrib : Attribute, new()
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return value.GetEnumAttribute<TEnum, TAttrib>().Coalesce(attribPropName, defaultValue);
        }

        public static TOut GetEnumAttributeProperty<TEnum, TOut>(this TEnum value, string attribName, string attribPropName, TOut defaultValue = default(TOut))
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return value.GetEnumAttribute<TEnum>(attribName).Coalesce(attribPropName, defaultValue);
        }
        #endregion Enum attribute extensions
        #endregion

        #region Methods
        public static string ToString()
        {
            return Utilities.ToString(typeof(AttributeExtensions));
        }

        private static string ExtractAttribName(string attribName)
        {
            string sAttribute = "Attribute";
            attribName = attribName.Trim().Replace(" ", "");

            return !attribName.Contains(sAttribute) ? attribName
                : attribName.TrimAndLeave(sAttribute.Length, eTrimDirection.Right);
        }
        #endregion
    }
}
